﻿using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using TodoSoft.Json;

namespace JsonTestConsole
{
	internal class SerializeCsBuilder : ISerializeMethodBuilder, ISerializeTypeBuilder
	{
		List<Action<object,TextWriter>> _actions;
				
		string _className = "Converter";
		string _methodName="Convert";
		
		List<Type> _types;
		TextWriter _textWriter;
		
		Type _generatedType;
		StringBuilder sb = new StringBuilder(1024);
			
		
		public SerializeCsBuilder ()
		{
			_generatedType = null;
			_types = new List<Type>();
			
			_textWriter = new StringWriter(sb);
			Using("System.IO");
			
			_textWriter.Write("internal class ");
			_textWriter.WriteLine(_className);
			_textWriter.WriteLine("{");

		}
		
		#region ISerializeCodeGenerator implementation
		public void ConfigureConvertionMethod(Type type, Action<ISerializeMethodBuilder> action)
		{
			if(_types.Contains(type))
				throw new Exception("The method for type " + type.FullName+" alredy configured.");
			
			_types.Add(type);
			
			ConvertionMethod(type, action);
		}
		
		void Compile()
		{
			_textWriter.WriteLine("}");
			
			var code = sb.ToString();
			
			using (CSharpCodeProvider provider = new CSharpCodeProvider ())
        	{
				var assemblies = new List<string>();
				assemblies.Add(typeof(TextWriter).Assembly.Location);
				
				foreach(var type in _types)
				{
					assemblies.Add(type.Assembly.Location);
				}
				
				
 	            var compileResult = provider.CompileAssemblyFromSource (new CompilerParameters (assemblies.ToArray()) {
					GenerateInMemory = true}, code);
 
            	_generatedType = compileResult.CompiledAssembly.GetType (_className);
        	}
		}
		
		public Action<object, TextWriter> GetConvertionMethod(Type type)
		{
			if(_generatedType == null)
				Compile();
		
			var method = _generatedType.GetMethod(_methodName,  new []{type, typeof(TextWriter)});
		
			//return (Action<object, TextWriter>)Delegate.CreateDelegate(typeof(Action<object, TextWriter>), method);
			return (obj,textWriter)=> method.Invoke(null, new object[]{obj, textWriter});
		}
		#endregion
		
		public void ConvertionMethod(Type type, Action<ISerializeMethodBuilder> action)
		{
			Method(_textWriter, "public static void", _methodName, "("+type.FullName.Replace('+','.')+" obj, TextWriter textWriter)", (method) => {
				action(this);
			});
		}
		
		public void CallConvertionMethod(string propertyName)
		{
			_textWriter.Write(_methodName+"(obj."+propertyName+", textWriter);");
		}
		
		short _varIndex = 0;
		
		string GetVarName()
		{
			_varIndex++;
			return "v" + _varIndex;
		}
		
		public short CreateStringVar(string initValue)
		{
			var varName = GetVarName();
			_textWriter.Write("string "+varName);
			if(initValue != null)
			{
				_textWriter.Write("=\""+initValue.Replace("\\","\\\\").Replace("\"","\\\"")+"\"");
			}
			_textWriter.WriteLine(";");
			return _varIndex;
		}
		
		public void SetVar(short varIndex, string varVal)
		{
			var varName = "v"+varIndex;
			
			_textWriter.Write(varName + " = ");
			if(varVal == null)
			{
				_textWriter.WriteLine("null;");
			}
			else
			{
				_textWriter.WriteLine("\""+varVal.Replace("\\","\\\\").Replace("\"","\\\"")+"\";");
			}
		}
		
		public void Foreach(string name, Action<ISerializeMethodBuilder, short> action)
		{
			var varName = GetVarName();
			
			_textWriter.WriteLine("foreach(var "+varName+" in obj."+name+"){");
			action(this, _varIndex);
			_textWriter.WriteLine("}");
		}
		
		public void WriteProperty(string name)
		{
			//_textWriter.WriteLine("if(!first)	textWriter.Write(\",\");	else first = false;");
			
			
			CallMethod("textWriter.Write","obj."+name);
			
		}
		
		
		public void IfNotNull(string name, Action<ISerializeMethodBuilder> action)
		{
			_textWriter.WriteLine("if (obj."+name+" != null){");
			action(this);
			_textWriter.WriteLine("}");	
		}
			
		public void EchoVar(short varName)
		{
			CallMethod("textWriter.Write","v"+varName);
		}
		
			
		public void EchoText(string text)
		{
			CallMethod("textWriter.Write","\""+ text.Replace("\\","\\\\").Replace("\"","\\\"")+"\"");
		}
		
		public void CallWriteLine(string text)
		{
			CallMethod("textWriter.WriteLine", "\""+ text.Replace("\\","\\\\").Replace("\"","\\\"")+"\"");
		}
		
		public void CallMethod(string methodName, string args)
		{
			_textWriter.Write(methodName);
			_textWriter.Write("(");
			_textWriter.Write(args);
			_textWriter.WriteLine(");");
		}
				
		private void Using(string nameSpace)
		{
			_textWriter.Write("using ");
			_textWriter.Write(nameSpace);
			_textWriter.WriteLine(";");
		}
		
		private void Method(
			TextWriter textWriter,
			string methodModifiers, 
			string methodName,
			string methodParams, 
			Action<TextWriter> methodBody)
		{
			textWriter.Write(methodModifiers);
			textWriter.Write(" ");
			textWriter.Write(methodName);
			textWriter.WriteLine(methodParams);
			Block(textWriter, methodBody);
		}
		
		private void Class(TextWriter textWriter, string className, Action<TextWriter> createClassBody)
		{
			textWriter.Write("internal class ");
			textWriter.WriteLine(className);
			Block(textWriter, createClassBody);
		}
		
		private void Block(TextWriter textWriter, Action<TextWriter> blockBody)
		{
			textWriter.WriteLine("{");
			
			if(blockBody != null)
				blockBody(textWriter);
						
			textWriter.WriteLine("}");
		}
	}
}
